Preamble

Dependencies

Show the code
source("utils.R")
theme_set(theme_light())

Setup and Preprocessing

Show the code
# taken from https://pachterlab.github.io/voyager/articles/visium_10x.html
#spe_vis <- readRDS("../data/spe_spot.rds")
#spe_vis

sfe_full <- SFEData::McKellarMuscleData(dataset = "full")

sfe_full <- mirrorImg(sfe_full, sample_id = "Vis5A", image_id = "lowres")
sfe <- sfe_full[,colData(sfe_full)$in_tissue]
sfe <- sfe[rowSums(counts(sfe)) > 0,]

#perform normalisation 
sfe <- scater::logNormCounts(sfe)

colGraph(sfe, "visium") <- findVisiumGraph(sfe)

Given this data from McKellar et al. we choose two genes to analyse henceforth, named Mdk (ENSMUSG00000027239) and Ncl (ENSMUSG00000026234) (McKellar et al. 2021).

Show the code
MdK <- "ENSMUSG00000027239"
NcI <- "ENSMUSG00000026234"
Show the code
features <- c("ENSMUSG00000027239", "ENSMUSG00000026234") # MdK, Ncl
colGraphName <- "visium"
colGeometryName <- "spotPoly"
segmentation <- "spotPoly"

Regular lattice and neighbourhood matrix

Spot based data is collected along a regular spaced grid where all sample areas have the same size. Such a grid is also called a regular lattice. In more rigorous terms the data \(Y\) is the product of a random process but the sampling locations are fixed along a lattice \(D\). The lattice \(D\) does not have to regular but in the scope of spot based data it is. The main difference of this type of data in comparison to point patterns is, that the locations of the data are then not results of a stochastic process but rather due to a defined sampling strategy (Zuur, Ieno, and Smith 2007).

The lattice is composed of individual spatial units

\[D = \{A_1, A_2,...,A_n\}\]

where these units are not supposed to overlap

\[A_i \cap A_j = \emptyset \forall i \neq j\]

The data is then a random variable of the spatial unit along the lattice

\[Y_i = Y(A_i)\]

Most lattice data analysis technique build on the concept of neighbours. Therefore, the spatial relationship has to be modelled with e.g. a spatial weigth matrix \(W\). There are a lot of ways to define a spatial weigth matrix \(W\). Here, the units that are adjacent are specified with a one and the ones that are not adjacent with a zero (binary coniguity matrix)

\[w_{ij} = \begin{cases} 1 \text{ if } A_i \text{ and } A_j \text{ are adjacent}\\ 0 \text{ otw} \end{cases}\]

other options to specify the weight matrix \(W\) are mentioned in Zuur, Ieno, and Smith (2007).

Voyager has a special function for the construction of the weight matrix in Visium data findVisiumGraph.

Global Measures for Bivariate Data

Global Bivariate Moran’s I

For two continous observation the global bivariate Moran’s I is defined as (Wartenberg 1985; Bivand 2022)

\[I_B = \frac{\Sigma_i(\Sigma_j{w_{ij}y_j\times x_i})}{\Sigma_i{x_i^2}}\]

where \(x_i\) and \(y_i\) are the two variables of interest and \(w_{ij}\) is the value of the spatial weights matrix for positions \(i\) and \(j\).

The global bivariate Moran’s I is a measure of autocorrelation of the variables \(x\) and \(y\) with the spatial lag of \(y\). Therefore the result might overestimate the spatial autocorrelation of the variables due to the inherent (non-spatial) correlation of \(x\) and \(y\) (Bivand 2022).

Implementation using spded

Show the code
res_xy <- spdep::moran_bv(x = logcounts(sfe)[features[1],],
         y = logcounts(sfe)[features[2],],
         listw =  colGraph(sfe, colGraphName),
         nsim = 499)
boot::boot.ci(res_xy, conf = c(0.99, 0.95, 0.9), type = "basic")
BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS
Based on 499 bootstrap replicates

CALL : 
boot::boot.ci(boot.out = res_xy, conf = c(0.99, 0.95, 0.9), type = "basic")

Intervals : 
Level      Basic         
99%   (-0.0057,  0.0674 )   
95%   ( 0.0034,  0.0602 )   
90%   ( 0.0084,  0.0556 )  
Calculations and Intervals on Original Scale
Some basic intervals may be unstable
Show the code
plot(res_xy)

From the result of the global measure, the overall spatial autocorrelation of the two genes is not significant.

Global Bivariate Lee’s L

Lee’s L is a bivariate measure that combines non-spatial pearson correlation with spatial autocorrelation via Moran’s I (Lee 2001). This enables us to asses the spatial dependence of two continuous variables in a single measure. The measure is defined as

\[L(x,y) = \frac{n}{\sum_{i=1}^n(\sum_{j=1}^nw_{ij})^2}\frac{\sum_{i=1}^n(\sum_{j=1}^nw_{ij}(x_i-\bar{x}))(\sum_{j=1}^nw_{ij}(y_j-\bar{y}))}{\sqrt{\sum_{i=1}^nw_{ij}(x_i-\bar{x})^2}\sqrt{\sum_{i=1}^nw_{ij}(y_i-\bar{y})^2}},\]

where \(w_{ij}\) is the value of the spatial weights matrix for positions \(i\) and \(j\), \(x\) and \(y\) the two variables and \(\bar{x}\) and \(\bar{y}\) their means.

Implementation using voyager

Show the code
res_lee <- calculateBivariate(sfe, type = "lee.mc", 
                   feature1 = features[1], feature2 = features[2],
                   colGraphName = colGraphName,
                   nsim = 499)
res_lee$lee.mc_statistic
statistic 
0.0213312 
Show the code
res_lee$ lee.mc_p.value
[1] 0.008

Local Measures for Bivariate Data

Bivariate Lee’s L

Similar to the global variant of Lee’s L the local variant (Lee 2001) is defined as

\[L_i(x,y) = \frac{\sum_{i=1}^n(\sum_{j=1}^nw_{ij}(x_i-\bar{x}))(\sum_{j=1}^nw_{ij}(y_j-\bar{y}))}{\sqrt{\sum_{i=1}^nw_{ij}(x_i-\bar{x})^2}\sqrt{\sum_{i=1}^nw_{ij}(y_i-\bar{y})^2}},\] Local Lee’s L is a measure of spatial co-expression, when the variables of interest are gene expression measurements and can also be a metric of co-localization. Unlike the gobal version, the variables are not averaged and show the local contribution to the metric. Positive values indicate colocalization, negative values indicate segregation.

This can be interesting in the context of detection of coexpressed ligand-receptor pairs. A method that is based on bivariate Moran’s I and tries to detect such pairs is SpatialDM (Li et al. 2023).

Bivariate Lee’s L

Implementation using voyager
Show the code
sfe_tissue <- runBivariate(sfe, type = "locallee",
                    feature1 = features[1], feature2 = features[2],
                    colGraphName = colGraphName)

plotLocalResult(sfe_tissue, "locallee", 
                 features = localResultFeatures(sfe_tissue, "locallee"),
                ncol = 2, divergent = TRUE, diverge_center = 0,
                colGeometryName = colGeometryName) 

Local Measures for Multivariate Data

Multivariate local Geary’s C

Geary’s C is a measure of spatial autocorrelation that is based on the difference between a variable and its neighbours. (Anselin 2019) defines it as

\[C_i = \sum_{j=1}^n w_{ij}(z_i-z_j)^2,\]

and can be generalized to \(k\) parameters by expanding

\[c_{k,i} = \sum_{v=1}^k c_{v,i}\]

where \(c_{v,i}\) is the local Geary’s C for the \(v\)th variable at location \(i\). The number of variables that can be used is not fixed, which makes the interpretation a bit more difficult. In general, the metric summarizes similarity in the “multivariate attribute space” (i.e. the gene expression) to its geographic neighbours. The common difficulty in these analyses is the interpretation of the mixture of similarity in the geographic space and similarity in the attribute space.

Multivariate local Geary’s C

To speed up computation we will use highly variable genes.

Show the code
hvgs <- getTopHVGs(sfe, fdr.threshold = 0.01)

# Subset of the tissue
sfe_tissue <- runMultivariate(sfe, type = "localC_multi",
                    subset_row = hvgs,
                    colGraphName = colGraphName)

# Local C mutli is stored in colData so this is a workaround to plot it
plotSpatialFeature(sfe_tissue, "localC_multi")

We can further plot the results of the permutation test. Significant values indicate interesting regions, but should be interpreted with care for various reasons. For example, we are looking for similarity in a combination of multiple values but the exact combination is not known. Anselin (2019) write “Overall, however, the statistic indicates a combination of the notion of distance in multi-attribute space with that of geographic neighbors. This is the essence of any spatial autocorrelation statistic. It is also the trade-off encountered in spatially constrained multivariate clustering methods (for a recent discussion, see, e.g., Grubesic, Wei, and Murray 2014).”. Multi-attribute space refers here to the highly variable genes. The problem comes down to where the similarity comes from, the gene expression or the physical space. The same problem is common in spatial domain detection methods.

Show the code
sfe <- runMultivariate(sfe, type = "localC_perm_multi",
                    subset_row = hvgs,
                    nsim = 100,
                    colGraphName= colGraphName)

# stored as spatially reduced dim; plot it in this way
spatialReducedDim(sfe, "localC_perm_multi",  c(1, 11))

Local Neighbor Match Test

This test is useful to assess the overlap of the k-nearest neighbours from physical distances (tissue space) with the k-nearest neighbours from the gene expression measurements (attribute space). For both physical and attribute space k-nearest neighbor matrix is computed. In a second step the probability of an overlap between the two matrices have in common (Anselin and Li 2020).

Show the code
sf <- colGeometries(sfe)[[segmentation]]
sf <- cbind(sf,  t(as.matrix(logcounts(sfe)[hvgs,])))

nbr_test <- neighbor_match_test(sf[c(hvgs)], k = 20)

sf$Probability <- nbr_test$Probability
sf$Cardinality <- nbr_test$Cardinality

tm_shape(sf) + tm_fill(col = 'Cardinality')  

Show the code
tm_shape(sf) + tm_fill(col = 'Probability')  

Cardinality is a measure of how many neighbours of the two matrices are in common. Some regions show high cardinality with low probability therefore share similarity on both attribute and physical space. In contrast to multivariate local Geary’s C this metric focuses directly on the distances and not on a weighted average. A problem of this approach is called the empty space problem which states that as the number of dimensions of the feature sets increase, the empty space between observations also increases (Anselin and Li 2020).

Measures for binary and categorical data

In addition to measures of spatial autocorrelation of continuous data as seen above, there exist a method that apply the same concept to binary and categorical data, joint count statistics. In essence the joint count statistic compares the distribution of categorical marks in a lattice with frequencies that would occur randomly. These random occurrences can be computed using a theoretical approximation or random permutations. The same concept was also extended in a multivariate setting with more than two categories. The corresponding spdep function are called joincount.test and joincount.multi Bivand (2022).

A note of caution

The local methods presented above should always be interpreted with care, since we face the problem of multiple testing when calculating them for each cell. Moreover, the presented methods should mainly serve as exploratory measures to identify interesting regions in the data. Multiple processes can lead to the same pattern, thus from identifying the pattern we cannot infer the underlying process. Indication of clustering does not explain why this occurs. On the one hand, clustering can be the result of spatial interaction between the variables of interest. We have an accumulation of a gene of interest in one region of the tissue. On the other hand clustering can be the result spatial heterogeneity, when local similarity is created by structural heterogeneity in the tissue, e.g., that cells with uniform expression of a gene of interest are grouped together which then creates the apparent clustering of the gene expression measurement.

Session info

Show the code
sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.5

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/Zurich
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] magrittr_2.0.3                 stringr_1.5.0                 
 [3] dixon_0.0-8                    splancs_2.01-44               
 [5] spdep_1.2-8                    spData_2.3.0                  
 [7] tmap_3.3-4                     scater_1.28.0                 
 [9] scran_1.28.2                   scuttle_1.10.3                
[11] SFEData_1.2.0                  SpatialFeatureExperiment_1.2.3
[13] Voyager_1.2.7                  rgeoda_0.0.10-4               
[15] digest_0.6.33                  ncf_1.3-2                     
[17] sf_1.0-16                      reshape2_1.4.4                
[19] patchwork_1.2.0                STexampleData_1.8.0           
[21] ExperimentHub_2.8.1            AnnotationHub_3.8.0           
[23] BiocFileCache_2.8.0            dbplyr_2.3.4                  
[25] RANN_2.6.1                     seg_0.5-7                     
[27] sp_2.1-1                       rlang_1.1.1                   
[29] ggplot2_3.5.1                  dplyr_1.1.3                   
[31] mixR_0.2.0                     spatstat_3.0-6                
[33] spatstat.linnet_3.1-1          spatstat.model_3.2-6          
[35] rpart_4.1.19                   spatstat.explore_3.2-3        
[37] nlme_3.1-162                   spatstat.random_3.1-6         
[39] spatstat.geom_3.2-5            spatstat.data_3.0-1           
[41] SpatialExperiment_1.10.0       SingleCellExperiment_1.22.0   
[43] SummarizedExperiment_1.30.2    Biobase_2.60.0                
[45] GenomicRanges_1.52.1           GenomeInfoDb_1.36.4           
[47] IRanges_2.34.1                 S4Vectors_0.38.2              
[49] BiocGenerics_0.46.0            MatrixGenerics_1.12.3         
[51] matrixStats_1.0.0             

loaded via a namespace (and not attached):
  [1] spatstat.sparse_3.0-2         bitops_1.0-7                 
  [3] httr_1.4.7                    RColorBrewer_1.1-3           
  [5] tools_4.3.1                   utf8_1.2.3                   
  [7] R6_2.5.1                      HDF5Array_1.28.1             
  [9] mgcv_1.9-1                    rhdf5filters_1.12.1          
 [11] withr_2.5.1                   gridExtra_2.3                
 [13] leaflet_2.2.0                 leafem_0.2.3                 
 [15] cli_3.6.1                     labeling_0.4.3               
 [17] proxy_0.4-27                  dbscan_1.1-11                
 [19] R.utils_2.12.2                dichromat_2.0-0.1            
 [21] scico_1.5.0                   limma_3.56.2                 
 [23] rstudioapi_0.15.0             RSQLite_2.3.1                
 [25] generics_0.1.3                crosstalk_1.2.0              
 [27] Matrix_1.5-4.1                ggbeeswarm_0.7.2             
 [29] fansi_1.0.5                   abind_1.4-5                  
 [31] R.methodsS3_1.8.2             terra_1.7-55                 
 [33] lifecycle_1.0.3               yaml_2.3.7                   
 [35] edgeR_3.42.4                  rhdf5_2.44.0                 
 [37] tmaptools_3.1-1               grid_4.3.1                   
 [39] blob_1.2.4                    promises_1.2.1               
 [41] dqrng_0.3.1                   crayon_1.5.2                 
 [43] lattice_0.21-8                beachmat_2.16.0              
 [45] KEGGREST_1.40.1               magick_2.8.0                 
 [47] pillar_1.9.0                  knitr_1.44                   
 [49] metapod_1.7.0                 rjson_0.2.21                 
 [51] boot_1.3-28.1                 codetools_0.2-19             
 [53] wk_0.8.0                      glue_1.6.2                   
 [55] vctrs_0.6.4                   png_0.1-8                    
 [57] gtable_0.3.4                  cachem_1.0.8                 
 [59] xfun_0.40                     S4Arrays_1.0.6               
 [61] mime_0.12                     DropletUtils_1.20.0          
 [63] units_0.8-4                   statmod_1.5.0                
 [65] bluster_1.10.0                interactiveDisplayBase_1.38.0
 [67] ellipsis_0.3.2                bit64_4.0.5                  
 [69] filelock_1.0.2                irlba_2.3.5.1                
 [71] vipor_0.4.5                   KernSmooth_2.23-21           
 [73] colorspace_2.1-0              DBI_1.1.3                    
 [75] raster_3.6-26                 tidyselect_1.2.0             
 [77] bit_4.0.5                     compiler_4.3.1               
 [79] curl_5.1.0                    BiocNeighbors_1.18.0         
 [81] DelayedArray_0.26.7           scales_1.3.0                 
 [83] classInt_0.4-10               rappdirs_0.3.3               
 [85] goftest_1.2-3                 spatstat.utils_3.0-5         
 [87] rmarkdown_2.25                XVector_0.40.0               
 [89] htmltools_0.5.6.1             pkgconfig_2.0.3              
 [91] base64enc_0.1-3               sparseMatrixStats_1.12.2     
 [93] fastmap_1.1.1                 htmlwidgets_1.6.2            
 [95] shiny_1.7.5.1                 DelayedMatrixStats_1.22.6    
 [97] farver_2.1.1                  jsonlite_1.8.7               
 [99] BiocParallel_1.34.2           R.oo_1.25.0                  
[101] BiocSingular_1.16.0           RCurl_1.98-1.12              
[103] GenomeInfoDbData_1.2.10       s2_1.1.4                     
[105] Rhdf5lib_1.22.1               munsell_0.5.0                
[107] Rcpp_1.0.11                   ggnewscale_0.4.9             
[109] viridis_0.6.4                 stringi_1.7.12               
[111] leafsync_0.1.0                zlibbioc_1.46.0              
[113] plyr_1.8.9                    parallel_4.3.1               
[115] ggrepel_0.9.4                 deldir_1.0-9                 
[117] Biostrings_2.68.1             stars_0.6-4                  
[119] splines_4.3.1                 tensor_1.5                   
[121] locfit_1.5-9.8                igraph_1.5.1                 
[123] ScaledMatrix_1.8.1            BiocVersion_3.17.1           
[125] XML_3.99-0.14                 evaluate_0.22                
[127] BiocManager_1.30.22           httpuv_1.6.11                
[129] purrr_1.0.2                   polyclip_1.10-6              
[131] rsvd_1.0.5                    lwgeom_0.2-13                
[133] xtable_1.8-4                  e1071_1.7-13                 
[135] RSpectra_0.16-1               later_1.3.1                  
[137] viridisLite_0.4.2             class_7.3-22                 
[139] tibble_3.2.1                  memoise_2.0.1                
[141] beeswarm_0.4.0                AnnotationDbi_1.62.2         
[143] cluster_2.1.4                

©2024 The pasta authors. Content is published under Creative Commons CC-BY-4.0 License for the text and GPL-3 License for any code.

References

Anselin, Luc. 2019. “A Local Indicator of Multivariate Spatial Association: Extending Geary’s c.” Geographical Analysis 51 (2): 133–50. https://doi.org/10.1111/gean.12164.
Anselin, Luc, and Xun Li. 2020. “Tobler’s Law in a Multivariate World.” Geographical Analysis 52 (4): 494–510. https://doi.org/https://doi.org/10.1111/gean.12237.
Bivand, Roger. 2022. “R Packages for Analyzing Spatial Data: A Comparative Case Study with Areal Data.” Geographical Analysis 54 (3): 488–518. https://doi.org/10.1111/gean.12319.
Dale, Mark R. T., and Marie-Josée Fortin. 2014. Spatial Analysis: A Guide for Ecologists. Second Edition. Cambridge ; New York: Cambridge University Press.
Lee, Sang-Il. 2001. “Developing a Bivariate Spatial Association Measure: An Integration of Pearson’s r and Moran’s I.” Journal of Geographical Systems 3 (4): 369–85. https://doi.org/10.1007/s101090100064.
Li, Zhuoxuan, Tianjie Wang, Pentao Liu, and Yuanhua Huang. 2023. SpatialDM for Rapid Identification of Spatially Co-Expressed Ligand–Receptor and Revealing Cell–Cell Communication Patterns.” Nature Communications 14 (1): 3995. https://doi.org/10.1038/s41467-023-39608-w.
McKellar, David W., Lauren D. Walter, Leo T. Song, Madhav Mantri, Michael F. Z. Wang, Iwijn De Vlaminck, and Benjamin D. Cosgrove. 2021. “Large-Scale Integration of Single-Cell Transcriptomic Data Captures Transitional Progenitor States in Mouse Skeletal Muscle Regeneration.” Communications Biology 4 (1): 1–12. https://doi.org/10.1038/s42003-021-02810-x.
Wartenberg, Daniel. 1985. “Multivariate Spatial Correlation: A Method for Exploratory Geographical Analysis.” Geographical Analysis 17 (4): 263–83. https://doi.org/10.1111/j.1538-4632.1985.tb00849.x.
Zuur, Alain F., Elena N. Ieno, and Graham M. Smith. 2007. Analysing Ecological Data. Statistics for Biology and Health. New York: Springer.